<template>
    <canvas
        ref="canvasRef"
        class="app-sign-canvas" :id="domId"
        @mousedown.prevent.stop="handleMousedown"
        @mousemove.prevent.stop="handleMousemove"
        @mouseup.prevent.stop="handleMouseup"
        @mouseleave.prevent.stop="handleMouseleave">
        您的浏览器不支持canvas技术,请升级浏览器!
    </canvas>
</template>
<script>
// @touchstart.prevent.stop="handleTouchstart"
// @touchmove.prevent.stop="handleTouchmove"
// @touchend.prevent.stop="handleTouchend"
import { defineComponent, reactive, ref, onMounted, watch } from 'vue'
// 获取dom对象的偏移量 可以获取解决position定位的问题
// @returns number
const offset = (obj, direction) => {
  // 将top,left首字母大写,并拼接成offsetTop,offsetLeft
  const offsetDir = 'offset' + direction[0].toUpperCase() + direction.substring(1)
  let realNum = obj[offsetDir]
  let positionParent = obj.offsetParent // 获取上一级定位元素对象
  while (positionParent != null) {
    realNum += positionParent[offsetDir]
    positionParent = positionParent.offsetParent
  }
  return realNum
}

// 保存文件
const saveFile = (data, filename) => {
  const saveLink = document.createElementNS('http://www.w3.org/1999/xhtml', 'a')
  saveLink.href = data
  saveLink.download = filename
  const event = document.createEvent('MouseEvents')
  event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
  saveLink.dispatchEvent(event)
}

export default defineComponent({

  name: 'SignCanvas',

  emits: ['update:modelValue'],

  props: {
    options: { // 配置项
      required: false,
      type: [Object],
      default: () => null
    },
    modelValue: {
      required: false,
      type: String
    }
  },

  setup (props, ctx) {
    // let value = '' // base64
    const canvasRef = ref(null) // 生成唯一dom标识
    const domId = `sign-canvas-${Math.random().toString(36).substr(2)}` // 生成唯一dom标识
    let canvas = '' // canvas dom对象
    let context = '' // canvas 画布对象
    let dpr = 1
    let urlPath = [] // 绘制的路径
    let pathChild = [] // 记录当前这一笔的坐标
    const config = reactive({
      isFullScreen: false, // 是否全屏手写 [Boolean] 可选
      isFullCover: false, // 是否全屏模式下覆盖所有的元素 [Boolean] 可选
      isDpr: false, // 是否使用dpr兼容高分屏 [Boolean] 可选
      lastWriteSpeed: 1, // 书写速度 [Number] 可选
      lastWriteWidth: 2, // 下笔的宽度 [Number] 可选
      lineCap: 'round', // 线条的边缘类型 [butt]平直的边缘 [round]圆形线帽 [square]	正方形线帽
      lineJoin: 'round', // 线条交汇时边角的类型  [bevel]创建斜角 [round]创建圆角 [miter]创建尖角。
      canvasWidth: 600, // canvas宽高 [Number] 可选
      canvasHeight: 600, // 高度  [Number] 可选
      isShowBorder: true, // 是否显示边框 [可选]
      bgColor: '#fcc', // 背景色 [String] 可选 注:这背景色仅仅只是canvas背景,保存的图片仍然是透明的
      borderWidth: 1, // 网格线宽度  [Number] 可选
      borderColor: '#ff787f', // 网格颜色  [String] 可选
      writeWidth: 5, // 基础轨迹宽度  [Number] 可选
      maxWriteWidth: 30, // 写字模式最大线宽  [Number] 可选
      minWriteWidth: 5, // 写字模式最小线宽  [Number] 可选
      writeColor: '#101010', // 轨迹颜色  [String] 可选
      isSign: false, // 签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框
      imgType: 'png' // 下载的图片格式  [String] 可选为 jpeg  canvas本是透明背景的
    })

    let resizeTimer = null

    onMounted(() => {
      init()
      // 监听窗口变化
      window.addEventListener('resize', () => {
        if (resizeTimer) clearTimeout(resizeTimer)
        resizeTimer = setTimeout(() => {
          init()
        }, 100)
      })
    })

    watch(() => props.options, () => {
      init()
    }, { deep: true })

    // 开始写
    const writeBegin = (point, config, context) => {
      config.isWrite = true
      pathChild = []
      config.lastWriteTime = new Date().getTime()
      config.lastPoint = point
      // 轨迹样式
      context.beginPath()
      context.strokeStyle = config.writeColor
      context.lineCap = config.lineCap
      context.lineJoin = config.lineJoin
    }

    // 绘制轨迹
    const writing = (point) => {
      pathChild.push(point)
      context.moveTo(config.lastPoint.x * dpr, config.lastPoint.y * dpr)
      context.lineTo(point.x * dpr, point.y * dpr)
      setLineWidth()
      context.stroke()
      config.lastPoint = point
      context.closePath()
    }

    // 写结束
    const writeEnd = (point) => {
      config.isWrite = false
      config.lastPoint = point
      pathChild.push(point)
      urlPath.push(pathChild)
      saveAsImg()
    }

    // 清空画板
    const canvasClear = () => {
      urlPath = [] // 绘制的路径
      pathChild = [] // 记录当前这一笔的坐标
      context.save()
      context.strokeStyle = config.writeColor
      context.clearRect(0, 0, canvas.width, canvas.height)
      context.beginPath()
      context.lineWidth = config.borderWidth * dpr
      context.strokeStyle = config.borderColor
      const size = config.borderWidth / 2 * dpr
      if (config.isShowBorder) {
        // 画外面的框
        context.moveTo(size, size)
        context.lineTo(canvas.width - size, size)
        context.lineTo(canvas.width - size, canvas.height - size)
        context.lineTo(size, canvas.height - size)
        context.closePath()
        context.stroke()
      }
      if (config.isShowBorder && !config.isSign) {
        // 画里面的框
        context.moveTo(0, 0)
        context.lineTo(canvas.width, canvas.height)
        context.lineTo(canvas.width, canvas.height / 2)
        context.lineTo(0, canvas.height / 2)
        context.lineTo(0, canvas.height)
        context.lineTo(canvas.width, 0)
        context.lineTo(canvas.width / 2, 0)
        context.lineTo(canvas.width / 2, canvas.height)
        context.stroke()
      }
      ctx.emit('update:modelValue', '')
      context.restore()
    }

    // 保存图片 格式base64
    const saveAsImg = () => {
      pathChild = []
      urlPath = []
      const image = new Image()
      image.src = canvas.toDataURL(`image/${config.imgType}`)
      // value = image.src
      ctx.emit('update:modelValue', image.src)
      return image.src
    }

    const init = () => {
      const options = props.options
      if (options) {
        for (const key in options) {
          config[key] = options[key]
        }
      }
      dpr = typeof window !== 'undefined' && config.isDpr ? window.devicePixelRatio || window.webkitDevicePixelRatio || window.mozDevicePixelRatio || 1 : 1
      canvas = document.getElementById(domId)
      context = canvas.getContext('2d')
      canvas.style.background = config.bgColor
      if (config.isFullScreen) {
        config.canvasWidth = window.innerWidth || document.body.clientWidth
        config.canvasHeight = window.innerHeight || document.body.clientHeight
        if (config.isFullCover) { // 开启全屏覆盖
          canvas.style.position = 'fixed'
          canvas.style.top = 0
          canvas.style.left = 0
          canvas.style.margin = 0
          canvas.style.zIndex = 20001
        }
      }
      // canvas.height = config.canvasWidth
      // canvas.width = config.canvasHeight
      // 初始化画布
      canvas.width = config.canvasWidth * dpr
      canvas.height = config.canvasHeight * dpr
      canvas.style.width = `${config.canvasWidth}px`
      canvas.style.height = `${config.canvasHeight}px`
      config.emptyCanvas = canvas.toDataURL(`image/${config.imgType}`)

      canvasClear()
    }

    // 轨迹宽度
    const setLineWidth = () => {
      const nowTime = new Date().getTime()
      const diffTime = nowTime - config.lastWriteTime
      config.lastWriteTime = nowTime
      let returnNum = config.minWriteWidth + (config.maxWriteWidth - config.minWriteWidth) * diffTime / 30
      if (returnNum < config.minWriteWidth) {
        returnNum = config.minWriteWidth
      } else if (returnNum > config.maxWriteWidth) {
        returnNum = config.maxWriteWidth
      }
      returnNum = returnNum.toFixed(2)
      // 写字模式和签名模式
      if (config.isSign) {
        context.lineWidth = config.writeWidth * dpr
      } else {
        const lineWidth = config.lastWriteWidth = config.lastWriteWidth / 4 * 3 + returnNum / 4
        context.lineWidth = lineWidth * dpr
      }
    }

    // 鼠标按下 => 下笔
    const handleMousedown = (e) => {
      console.log('鼠标按下 => 下笔')
      writeBegin({ x: e.offsetX || e.clientX, y: e.offsetY || e.clientY }, config, context)
    }

    // 书写过程 => 下笔书写
    const handleMousemove = (e) => {
      if (config.isWrite) {
        console.log('书写过程 => 下笔书写')
        writing({ x: e.offsetX, y: e.offsetY })
      }
    }

    // 鼠标松开 => 提笔
    const handleMouseup = (e) => {
      console.log(`鼠标松开 => 提笔: x: ${e.offsetX} y: ${e.offsetY}`)
      writeEnd({ x: e.offsetX, y: e.offsetY })
      config.isWrite = false // 表示动作已经结束
    }

    // 离开书写区域 => 提笔离开
    const handleMouseleave = (e) => {
      // 等于 true 表示按下鼠标直接到画布外去了   没有在画布内抬起
      if (!config.isWrite) { return }
      console.log(`离开书写区域 => 提笔离开: x: ${e.offsetX} y: ${e.offsetY}`, config.isWrite)
      pathChild.push({ x: e.offsetX, y: e.offsetY })
      urlPath.push(pathChild)
      pathChild = []
      config.isWrite = false // 表示动作已经结束
    }

    /* ==========================移动端兼容=Start================================ */

    // 手指按下 => 下笔
    const handleTouchstart = (e) => {
      const touch = e.targetTouches[0]
      const x = touch.clientX ? touch.clientX - getRect().left : touch.pageX - offset(touch.target, 'left')
      const y = touch.clientY ? touch.clientY - getRect().top : touch.pageY - offset(touch.target, 'top')
      writeBegin({ x, y }, config, context)
    }

    // 手指移动 => 下笔书写
    const handleTouchmove = (e) => {
      const touch = e.targetTouches[0]
      const x = touch.clientX ? touch.clientX - getRect().left : touch.pageX - offset(touch.target, 'left')
      const y = touch.clientY ? touch.clientY - getRect().top : touch.pageY - offset(touch.target, 'top')
      config.isWrite && writing({ x, y })
    }

    // 手指移动结束 => 提笔离开
    const handleTouchend = (e) => {
      const tcs = e.targetTouches
      const ccs = e.changedTouches
      const touch = (tcs && tcs.length && tcs[0]) || (ccs && ccs.length && ccs[0])
      const x = touch.clientX ? touch.clientX - getRect().left : touch.pageX - offset(touch.target, 'left')
      const y = touch.clientY ? touch.clientY - getRect().top : touch.pageY - offset(touch.target, 'top')
      writeEnd({ x, y })
    }

    /* ==========================移动端兼容=End================================== */

    // 下载二维码到本地
    const downloadSignImg = (name) => {
      pathChild = []
      urlPath = []
      const c = document.getElementById(domId)
      const dataURL = c.toDataURL('image/png')
      saveFile(dataURL, name ? `${name}.${config.imgType}` : `${Date.parse(new Date())}.${config.imgType}`)
    }

    // 获取画布的元素的大小及其相对于视口的位置
    const getRect = () => {
      return canvasRef.value.getBoundingClientRect()
    }

    return {
      domId,
      canvasRef,
      downloadSignImg,
      handleTouchstart,
      handleTouchmove,
      handleTouchend,
      handleMouseleave,
      handleMousedown,
      handleMousemove,
      handleMouseup,
      canvasClear
    }
  }
})
</script>
